New upstream version 2.7.2+ds
authorIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Thu, 5 Mar 2026 07:32:06 +0000 (08:32 +0100)
committerIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Thu, 5 Mar 2026 07:32:06 +0000 (08:32 +0100)
15 files changed:
docs/Build/Linux.md
docs/Documentation/NetworkProtocol.md [new file with mode: 0644]
docs/changelog.yml
linux/Dockerfile.build
linux/README.md
meson.build
mkdocs.yml
src/JackAudioInterface.cpp
src/OscServer.cpp
src/Regulator.cpp
src/Regulator.h
src/UdpHubListener.cpp
src/jacktrip_globals.h
src/main.cpp
src/vs/virtualstudio.cpp

index 144624f8ba626faf52bd88fcf4b9b4e5710fac04..87e36a8c46a5fb8dc10bf9db5d11fb3cdfd29e1f 100644 (file)
@@ -43,7 +43,7 @@ apt install qtbase5-dev qtbase5-dev-tools qtchooser qt5-qmake qttools5-dev libqt
 ### Ubuntu and Debian/Raspbian (Qt6)
 ```sh
 apt install --no-install-recommends build-essential autoconf automake libtool make libjack-jackd2-dev git help2man libclang-dev libdbus-1-dev libdbus-1-dev python3-jinja2
-apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6  libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window
+apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6  libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window qml6-module-qtquick-dialogs
 apt install qt6-base-dev qt6-base-dev-tools qmake6 qt6-tools-dev qt6-declarative-dev qt6-webengine-dev qt6-webview-dev qt6-webview-plugins libqt6svg6-dev libqt6websockets6-dev libqt6core5compat6-dev libqt6shadertools6-dev libgl1-mesa-dev
 # for GUI builds
 apt install libfreetype6-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libx11-xcb-dev libdrm-dev libglu1-mesa-dev libwayland-dev libwayland-egl1-mesa libgles2-mesa-dev libwayland-server0 libwayland-egl-backend-dev libxcb1-dev libxext-dev libfontconfig1-dev libxrender-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev '^libxcb.*-dev' libxcb-render-util0-dev libxcomposite-dev libgtk-3-dev
diff --git a/docs/Documentation/NetworkProtocol.md b/docs/Documentation/NetworkProtocol.md
new file mode 100644 (file)
index 0000000..fefba86
--- /dev/null
@@ -0,0 +1,246 @@
+## JackTrip network protocol (as implemented)
+
+This document describes JackTrip’s **on-the-wire protocol** as implemented in the current source tree. It is intended for developers debugging or interoperating with JackTrip at the packet level.
+
+### Scope and non-goals
+
+- **In scope**: the real-time **UDP audio stream**, its headers and payload layout, the optional **UDP redundancy** framing, the small **UDP “stop” control packet**, and the **TCP handshake** used by hub/ping-server style deployments (including the authentication variant).
+- **Out of scope**: local-only IPC (e.g. `QLocalSocket` “AudioSocket”), OSC control, and any higher-level application semantics outside packet exchange.
+
+### Transports at a glance
+
+- **UDP (audio)**: real-time audio is sent as UDP datagrams containing `PacketHeader` + raw audio payload.
+- **UDP (control)**: a small fixed-size “stop” datagram is used to signal shutdown.
+- **TCP (hub/ping-server handshake)**: a short-lived TCP connection is used to exchange ephemeral UDP port information (and optionally do TLS + credentials). The client sends 4 bytes representing the port number it is binding to, and the server responds by sending 4 bytes representing its own port number.
+
+---
+
+## UDP audio datagrams
+
+### High-level framing
+
+Each UDP datagram carries one of:
+
+- **Audio datagram**: one or more **full packets** (header + audio payload). When redundancy is disabled, there is exactly one full packet per UDP datagram. When redundancy is enabled, multiple full packets are concatenated into a single UDP datagram to provide forward error correction (FEC) (see “UDP redundancy”).
+- **Stop/control datagram**: exactly 63 bytes of `0xFF` (see “UDP stop/control datagram”).
+
+### Packet header types
+
+The header is selected by `DataProtocol::packetHeaderTypeT`:
+
+- **DEFAULT**: `DefaultHeaderStruct` (the standard JackTrip header).
+- **JAMLINK**: `JamLinkHeaderStuct` (JamLink compatibility).
+- **EMPTY**: no header (payload only).
+
+See `src/PacketHeader.h` and `src/PacketHeader.cpp`.
+
+### Default header (`DEFAULT`)
+
+On-wire layout is the in-memory `DefaultHeaderStruct` copied with `memcpy()` (no explicit endian conversions).
+
+Fields (in order):
+
+| Field | Type | Meaning |
+|------:|------|---------|
+| `TimeStamp` | `uint64_t` | Timestamp in microseconds since Unix epoch (see `PacketHeader::usecTime()`). |
+| `SeqNumber` | `uint16_t` | Sequence number; increments once per audio period and wraps at 16 bits. |
+| `BufferSize` | `uint16_t` | Audio period size \(N\) in **samples per channel**. |
+| `SamplingRate` | `uint8_t` | Encoded sample-rate enum value (`AudioInterface::samplingRateT`), **not** Hz. |
+| `BitResolution` | `uint8_t` | Bits per sample (8/16/24/32). |
+| `NumIncomingChannelsFromNet` | `uint8_t` | Channel count expected from the peer “from network” direction (see notes below). |
+| `NumOutgoingChannelsToNet` | `uint8_t` | Channel count the sender is placing into the payload (see notes below). |
+
+#### Important interoperability notes
+
+- **Endianness / ABI**: this header is serialized by raw `memcpy()` of a C struct. In practice this assumes:
+  - both sides are using compatible ABI/layout for the struct, and
+  - both sides are on the same endianness (typically **little-endian** on modern desktop platforms).
+- **Channel fields are asymmetric**: the implementation uses these fields to convey “incoming vs outgoing” channel counts, including a couple of sentinel behaviors:
+  - `NumIncomingChannelsFromNet` is populated from local *audio interface output* channel count.
+  - `NumOutgoingChannelsToNet` may be set to `0` when in/out channel counts match, or to `0xFF` when there are zero audio interface input channels.
+
+These behaviors come from `DefaultHeader::fillHeaderCommonFromAudio()` in `src/PacketHeader.cpp`.
+
+### JamLink header (`JAMLINK`)
+
+Please note that JamLink is an obsolete device.
+
+JamLink uses a compact header:
+
+| Field | Type | Meaning |
+|------:|------|---------|
+| `Common` | `uint16_t` | Bitfield describing mono/stereo, bit depth, sample rate, and samples-per-packet (JamLink “streamType”). |
+| `SeqNumber` | `uint16_t` | Sequence number. |
+| `TimeStamp` | `uint32_t` | Timestamp. |
+
+The current implementation primarily fills this for JamLink constraints (mono, 48kHz, 64-sample buffers). See `JamLinkHeader::fillHeaderCommonFromAudio()` in `src/PacketHeader.cpp`.
+
+### Empty header (`EMPTY`)
+
+No header; the UDP payload is raw audio data only.
+
+---
+
+## UDP audio payload
+
+### Size
+
+For a single full packet (no redundancy), the UDP payload length is:
+
+$$\text{headerBytes} + (N \times C \times \text{bytesPerSample})$$
+
+Where:
+
+- \(N\) is `BufferSize` (samples per channel)
+- \(C\) is the number of channels present in the payload
+- `bytesPerSample` is `BitResolution / 8`
+
+### Channel/sample ordering (planar / non-interleaved)
+
+On the wire, the payload is **planar** (non-interleaved) by channel:
+
+- First \(N\) samples for channel 0
+- Then \(N\) samples for channel 1
+- …
+
+This is explicit in `UdpDataProtocol` which converts between:
+
+- **Internal**: interleaved layout \([n][c]\)
+- **Network**: planar layout \([c][n]\)
+
+See `UdpDataProtocol::sendPacketRedundancy()` and `UdpDataProtocol::receivePacketRedundancy()` in `src/UdpDataProtocol.cpp`.
+
+### Sample encoding (bit resolution)
+
+JackTrip processes audio internally as `float` (`sample_t`), but the network payload uses the selected bit resolution via `AudioInterface::fromSampleToBitConversion()` / `fromBitToSampleConversion()`.
+
+Behavior by bit resolution (`AudioInterface::audioBitResolutionT`):
+
+- **8-bit (`BIT8`)**: signed 8-bit integer, scaled from float in \([-1, 1]\).
+- **16-bit (`BIT16`)**: signed 16-bit integer, written **little-endian**.
+- **24-bit (`BIT24`)**: a **non-standard 3-byte format**: a 16-bit signed integer plus an 8-bit unsigned “remainder” byte.
+- **32-bit (`BIT32`)**: raw 32-bit float bytes (`memcpy` of `float`), which implicitly assumes IEEE-754 and matching endianness.
+
+See `src/AudioInterface.cpp`.
+
+---
+
+## UDP redundancy (optional)
+
+JackTrip can send redundant audio packets to reduce audible artifacts from packet loss.
+
+### Framing
+
+With redundancy factor \(R\), each UDP datagram contains **R full packets** concatenated:
+
+- The newest packet is first (`UDP[n]`), followed by older packets (`UDP[n-1]`, …).
+- Total UDP payload length becomes `R * full_packet_size`.
+
+The sender implements this by shifting a buffer and prepending the newest full packet each period.
+
+See `UdpDataProtocol::sendPacketRedundancy()` and the explanatory comment block in `src/UdpDataProtocol.cpp`.
+
+### Receiver behavior
+
+Upon receiving a redundant datagram, the receiver:
+
+- Reads the first packet’s `SeqNumber`.
+- If it is not the next expected sequence, scans forward through the concatenated packets looking for the expected next one.
+- May “revive” and deliver multiple packets from the redundant datagram in order.
+- Treats large negative or implausibly large sequence jumps as **out-of-order** and ignores them.
+
+See `UdpDataProtocol::receivePacketRedundancy()` in `src/UdpDataProtocol.cpp`.
+
+---
+
+## UDP stop/control datagram
+
+JackTrip uses a special fixed-size UDP datagram to signal shutdown:
+
+- **Length**: 63 bytes
+- **Contents**: every byte is `0xFF`
+
+The receiver checks for this exact pattern and treats it as “Peer Stopped”.
+
+See `UdpDataProtocol::processControlPacket()` and the shutdown path in `UdpDataProtocol::run()` in `src/UdpDataProtocol.cpp`.
+
+---
+
+## Connection setup and “handshake”
+
+JackTrip supports multiple deployment styles. The relevant “protocol” differs depending on mode.
+
+### P2P server mode (UDP-only)
+
+In P2P server mode, there is **no TCP handshake**. Instead:
+
+- The server binds a UDP socket on its configured receive port.
+- It waits for the first UDP datagram.
+- It uses the datagram’s source address/port as the peer endpoint for subsequent UDP send/receive.
+
+This supports basic NAT traversal by responding to the client’s observed source port.
+
+See `JackTrip::serverStart()` and `JackTrip::receivedDataUDP()` in `src/JackTrip.cpp`.
+
+### Hub / ping-server mode (TCP handshake + UDP audio)
+
+When connecting to a hub/ping-server style endpoint, JackTrip uses a short-lived TCP connection to exchange UDP port information.
+
+#### Unauthenticated handshake (no TLS)
+
+Client → server (TCP):
+
+- `int32` little-endian: the client’s UDP receive/bind port
+- `gMaxRemoteNameLength` bytes: optional UTF-8 “remote client name” (null-terminated, padded with zeros)
+
+Server → client (TCP):
+
+- `int32` little-endian: the server-assigned UDP port the client should use as its peer port
+
+The TCP connection is then closed.
+
+Client-side send/receive logic: `JackTrip::receivedConnectionTCP()` and `JackTrip::receivedDataTCP()` in `src/JackTrip.cpp`  
+Server-side receive/send logic: `UdpHubListener::readClientUdpPort()` and `UdpHubListener::sendUdpPort()` in `src/UdpHubListener.cpp`
+
+#### Authentication / TLS handshake (optional)
+
+This is an extension of the same TCP handshake using values above 65535 as “auth response” codes.
+
+High-level flow:
+
+1. Client connects TCP and sends an `int32` little-endian value of `Auth::OK` to request authentication.
+2. Server replies with an `int32` auth response (e.g. `Auth::OK`, `Auth::NOTREQUIRED`, `Auth::REQUIRED`, …).
+3. If both sides proceed, TLS is established on the same TCP socket.
+4. Client then sends:
+   - `int32` LE: UDP receive/bind port
+   - `gMaxRemoteNameLength` bytes: client name
+   - `int32` LE: username length (excluding null terminator)
+   - `int32` LE: password length (excluding null terminator)
+   - `username` bytes + `\0`
+   - `password` bytes + `\0`
+5. Server validates credentials and replies with either:
+   - `int32` LE UDP port (<= 65535) on success, or
+   - `int32` LE auth error code (> 65535) on failure
+
+Client-side: `JackTrip::receivedConnectionTCP()`, `JackTrip::connectionSecured()`, and `JackTrip::receivedDataTCP()` in `src/JackTrip.cpp`  
+Server-side: `UdpHubListener::receivedClientInfo()`, `UdpHubListener::checkAuthAndReadPort()`, and `UdpHubListener::sendUdpPort()` in `src/UdpHubListener.cpp`
+
+---
+
+## QoS marking (best-effort)
+
+On supported platforms, JackTrip attempts to mark UDP packets as “voice” traffic:
+
+- Linux/Unix: sets DSCP to 56 (`IP_TOS` / `IPV6_TCLASS` set to `0xE0`), and sets `SO_PRIORITY` to 6.
+- Windows: uses QOS APIs with `QOSTrafficTypeVoice`.
+- macOS: uses `SO_NET_SERVICE_TYPE` with `NET_SERVICE_TYPE_VO` (best-effort).
+
+See `src/UdpDataProtocol.cpp`.
+
+---
+
+## References
+
+For additional context on JackTrip's network behavior and interpretation of debug output (`-V` flag):
+
+Chafe, C. (2018). I am Streaming in a Room. *Frontiers in Digital Humanities*, Volume 5. https://doi.org/10.3389/fdigh.2018.00027
\ No newline at end of file
index 1bf03510c787ea36f8e228aa444c3bcefb63cdce..ee60b87d655071518908f6b2db3d94ddd2c71ea7 100644 (file)
@@ -1,3 +1,14 @@
+- Version: "2.7.2"
+  Date: 2026-02-06
+  Description:
+  - (added) Documentation for the JackTrip network protocol
+  - (added) Hub server - log client name with UDP port
+  - (fixed) Fixed crash when JACK ran out of available ports
+  - (fixed) Refuse to run if DYLD_INSERT_LIBRARIES is set on OSX
+  - (fixed) Various PLC quality improvements and bug fixes
+  - (fixed) Improved error message when studio connection is lost
+  - (fixed) Suppressed verbose logging of OSC get requests
+  - (fixed) Added some missing Qt dependencies to Linux docs
 - Version: "2.7.1"
   Date: 2025-06-30
   Description:
index 307b143c2241ceb89fc8565bc15cc2444695e92f..9d30e16a6235b9e3ce5d5f5cb839d31034979bbb 100644 (file)
@@ -15,6 +15,11 @@ FROM ${BUILD_CONTAINER} AS builder
 
 # install required packages
 ENV DEBIAN_FRONTEND=noninteractive
+RUN DEBIAN_BUSTER=$(grep buster /etc/apt/sources.list); \
+  if [ -f /etc/apt/sources.list -a -n "$DEBIAN_BUSTER" ]; then \
+  sed -i s/deb.debian.org/archive.debian.org/g /etc/apt/sources.list; \
+  sed -i '/buster-updates/d' /etc/apt/sources.list; \
+  fi
 RUN apt-get update \
   && apt-get install -yq --no-install-recommends curl python3-pip build-essential git libclang-dev libdbus-1-dev cmake ninja-build libjack-dev \
   && apt-get install -yq --no-install-recommends libfreetype6-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libx11-xcb-dev libdrm-dev libglu1-mesa-dev libwayland-dev libwayland-egl1-mesa libgles2-mesa-dev libwayland-server0 libwayland-egl-backend-dev libxcb1-dev libxext-dev libfontconfig1-dev libxrender-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev '^libxcb.*-dev' libxcb-render-util0-dev libxcomposite-dev libgtk-3-dev \
index 072397ea98430a311687ce3b88b8c779d7cab73b..6e6ee33f91cdfd77f8f34db421e698457fa863d7 100644 (file)
@@ -13,7 +13,7 @@ dnf install -y qt6-qtbase qt6-qtbase-common qt6-qtbase-gui qt6-qtsvg qt6-qtwebso
 For Debian or Ubuntu:
 
 ```
-apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6  libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window qml6-module-qtquick-dialogs libjack-jackd2-0 librtaudio6 libxcb-cursor0
+apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6quickdialogs2-6 libqt6svg6  libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window qml6-module-qtquick-dialogs libjack-jackd2-0 librtaudio6 libxcb-cursor0
 ```
 
 To install JackTrip as a Linux desktop application:
index f0c21fcba89961e085fa06297804d9e523b28148..277dd02269a137729d88449d25d790cbe169be2a 100644 (file)
@@ -370,6 +370,7 @@ if get_option('libsamplerate').allowed()
                        opt_var.add_cmake_defines({'CMAKE_BUILD_TYPE': 'Debug'})
                endif
                opt_var.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})
+               opt_var.add_cmake_defines({'CMAKE_POLICY_VERSION_MINIMUM': '3.5'})
                libsamplerate_subproject = cmake.subproject('libsamplerate', options: opt_var)
                libsamplerate_dep = libsamplerate_subproject.dependency('samplerate')
                found_libsamplerate = libsamplerate_dep.found()
index 501de72a55530bc04c2660260ae2ee2e3f3c2dc5..286f79a60bf8b7d291dd073e55bba5bd9866d08b 100644 (file)
@@ -16,6 +16,7 @@ nav:
     - Development Tools:
       - Formatting: DevTools/Formatting.md
       - Static Analysis: DevTools/StaticAnalysis.md
+    - Network Protocol: Documentation/NetworkProtocol.md
     - Write Documentation: Documentation/MkDocs.md
   - About:
     - Contributors: About/Contributors.md
index b78b385b03948c1cd34b52683fa55c4f7197b8b6..af9ed010a08fabf2c2941209b2713c751ad4df29 100644 (file)
@@ -185,6 +185,11 @@ void JackAudioInterface::createChannels()
         mInPorts[i] =
             jack_port_register(mClient, inName.toLatin1(), JACK_DEFAULT_AUDIO_TYPE,
                                JackPortIsInput | JackPortIsTerminal, 0);
+        if (mInPorts[i] == nullptr) {
+            std::cerr << "*** JackAudioInterface.cpp: failed to register input port "
+                      << inName.toStdString() << "\n";
+            return;
+        }
     }
 
     // Create Output Ports
@@ -195,6 +200,11 @@ void JackAudioInterface::createChannels()
         mOutPorts[i] =
             jack_port_register(mClient, outName.toLatin1(), JACK_DEFAULT_AUDIO_TYPE,
                                JackPortIsOutput | JackPortIsTerminal, 0);
+        if (mOutPorts[i] == nullptr) {
+            std::cerr << "*** JackAudioInterface.cpp: failed to register output port "
+                      << outName.toStdString() << "\n";
+            return;
+        }
     }
     // Create Broadcast Ports
     if (mBroadcast) {
@@ -205,6 +215,11 @@ void JackAudioInterface::createChannels()
             mBroadcastPorts[i] =
                 jack_port_register(mClient, outName.toLatin1(), JACK_DEFAULT_AUDIO_TYPE,
                                    JackPortIsOutput | JackPortIsTerminal, 0);
+            if (mBroadcastPorts[i] == nullptr) {
+                std::cerr << "*** JackAudioInterface.cpp: failed to register broadcast "
+                          << "port " << outName.toStdString() << "\n";
+                return;
+            }
         }
     }
 }
@@ -308,10 +323,24 @@ int JackAudioInterface::processCallback(jack_nframes_t nframes)
         // Input Ports are READ ONLY and change as needed (no locks) - make a copy for
         // debugging
         mInBuffer[i] = (sample_t*)jack_port_get_buffer(mInPorts[i], nframes);
+        if (mInBuffer[i] == nullptr) {
+            std::cerr
+                << "*** JackAudioInterface.cpp: failed to get buffer for input port "
+                << mInPorts[i] << " channel " << i << "/" << getNumInputChannels()
+                << "\n";
+            return -1;
+        }
     }
     for (int i = 0; i < getNumOutputChannels(); i++) {
         // Output Ports are WRITABLE
         mOutBuffer[i] = (sample_t*)jack_port_get_buffer(mOutPorts[i], nframes);
+        if (mOutBuffer[i] == nullptr) {
+            std::cerr
+                << "*** JackAudioInterface.cpp: failed to get buffer for output port "
+                << mOutPorts[i] << " channel " << i << "/" << getNumOutputChannels()
+                << "\n";
+            return -1;
+        }
     }
     //-------------------------------------------------------------------
     // TEST: Loopback
index 30c94b6ba40ae5200e1010f1e2d383bd8de2a72a..2c356cc6377b43cf6e81e978f17b377dcfa550c2 100644 (file)
@@ -134,7 +134,7 @@ void OscServer::handlePacket(const OSCPP::Server::Packet& packet,
                 }
             } else if (msg == "/get") {
                 const char* key = args.string();
-                cout << "OSC: Get request received - key (" << key << ")" << endl;
+                // cout << "OSC: Get request received - key (" << key << ")" << endl;
                 if (strcmp("latency", key) == 0) {
                     emit signalLatencyRequested(sender, senderPort);
                 }
index 8b7cf4ce0cea79c423ccd03aa57a7a14c5d68d9a..798240836ee5a438c4e36e6784651a5ab66a82b1 100644 (file)
@@ -481,18 +481,8 @@ void Regulator::updateTolerance(int glitches, int skipped)
     // update headroom
     if (mAutoHeadroom < 0) {
         // variable headroom: automatically increase to minimize glitch counts
-        int glitchesAllowed;
-        if (mMsecTolerance >= (mPeerFPPdurMsec * 2)) {
-            // calculate glitches allowed if tolerance is above or equal to
-            // the duration of two packets
-            glitchesAllowed = std::ceil(
-                static_cast<float>(AutoHeadroomGlitchTolerance * mSampleRate) / mPeerFPP);
-        } else {
-            // zero glitches allowed if tolerance is below duration of two packets
-            glitchesAllowed = 0;
-            // also don't require two intervals in a row (override)
-            mSkipAutoHeadroom = false;
-        }
+        const int glitchesAllowed = std::ceil(
+            static_cast<float>(AutoHeadroomGlitchTolerance * mSampleRate) / mPeerFPP);
         // sanity check: prevent headroom from growing beyond the greater of
         // 3x rolling average of max, or 10ms higher than the max latency observed
         const int maxHeadroom =
@@ -507,7 +497,8 @@ void Regulator::updateTolerance(int glitches, int skipped)
                 if (mLastMaxLatency > mMsecTolerance + 1) {
                     // increase headroom enough to cover any skipped packets
                     mCurrentHeadroom = std::min<double>(
-                        maxHeadroom, std::ceil(mLastMaxLatency - mMsecTolerance));
+                        maxHeadroom,
+                        mCurrentHeadroom + std::ceil(mLastMaxLatency - mMsecTolerance));
                 } else {
                     ++mCurrentHeadroom;
                 }
@@ -630,15 +621,24 @@ bool Regulator::pullPacket()
     const double now       = (double)mIncomingTimer.nsecsElapsed() / 1000000.0;
     const int lastSeqNumIn = mLastSeqNumIn.load(std::memory_order_acquire);
     int skipped            = 0;
+    int firstGoodSkipped   = -1;
 
     if ((lastSeqNumIn == -1) || (!mInitialized) || (now < mMsecTolerance)) {
         // return silence during startup:
         // * no packets arrived yet
         // * not initialized
         // * hasn't run long enough to meet tolerance
-        goto ZERO_OUTPUT;
+        memset(mXfrBuffer, 0, mPeerBytes);
+        return false;
+    } else if (mStashedPacket != -1) {
+        // use the stashed packet as the best candidate
+        memcpy(mXfrBuffer, mSlots[mStashedPacket], mPeerBytes);
+        mLastSeqNumOut = mStashedPacket;
+        mStashedPacket = -1;
+        processPacket(false);
+        return false;
     } else if (lastSeqNumIn == mLastSeqNumOut) {
-        goto UNDERRUN;
+        return underrun(now, lastSeqNumIn);
     } else {
         // calculate how many new packets we want to look at to
         // find the next packet to pull
@@ -670,46 +670,50 @@ bool Regulator::pullPacket()
             }
             // check if packet's age matches tolerance, or is the best candidate we have
             if (mIncomingTiming[next] + mMsecTolerance >= now || i == 0) {
+                if (skipped == 1 && firstGoodSkipped >= 0) {
+                    // special case where we are about to skip 1 good packet.
+                    // this defers latency adjustments until they are at least
+                    // 2 packets wide.
+                    mStashedPacket = next;
+                    next           = firstGoodSkipped;
+                } else if (skipped > 0) {
+                    // process a glitch to account for the skipped packets,
+                    // but stash and use this good packet on next callback.
+                    pullStat->plcOverruns += skipped;
+                    mSkipped += skipped;
+                    mStashedPacket = next;
+                    processPacket(true);
+                    return true;
+                }
                 // next is the best candidate
                 memcpy(mXfrBuffer, mSlots[next], mPeerBytes);
                 mLastSeqNumOut = next;
-                goto PACKETOK;
+                processPacket(false);
+                return false;
             }
-            ++mSkipped;
+            if (firstGoodSkipped == -1)
+                firstGoodSkipped = next;
         }
-
-        // no viable candidate
-        goto UNDERRUN;
     }
 
-PACKETOK : {
-    pullStat->plcOverruns += skipped;
-    if (skipped && !mLastWasGlitch) {
-        processPacket(true);
-        return true;
-    } else
-        processPacket(false);
-    goto OUTPUT;
-}
+    // no viable candidate
+    return underrun(now, lastSeqNumIn);
+};
 
-UNDERRUN : {
+//*******************************************************************************
+bool Regulator::underrun(const double now, const int lastSeqNumIn)
+{
     pullStat->plcUnderruns++;  // count late
     if ((mLastSeqNumOut == lastSeqNumIn)
         && ((now - mIncomingTiming[mLastSeqNumOut]) > gUdpWaitTimeout)) {
-        goto ZERO_OUTPUT;
+        memset(mXfrBuffer, 0, mPeerBytes);
+        return false;
     }
     // "good underrun", not a stuck client
     processPacket(true);
     return true;
 }
 
-ZERO_OUTPUT:
-    memset(mXfrBuffer, 0, mPeerBytes);
-
-OUTPUT:
-    return false;
-};
-
 //*******************************************************************************
 void Regulator::processPacket(bool glitch)
 {
index a4a4b506d929d0673ebb873566da2e31161a838d..6041cdd4a58136e1f9f88bc26a29429c09559570 100644 (file)
@@ -236,7 +236,8 @@ class Regulator : public RingBuffer
    private:
     void pushPacket(const int8_t* buf, int seq_num);
     void updatePushStats(int seq_num);
-    bool pullPacket();    // returns true if PLC prediction
+    bool pullPacket();  // returns true if PLC prediction
+    bool underrun(const double now, const int lastSeqNumIn);
     bool enableWorker();  // returns true if worker was enabled
     void updateTolerance(int glitches, int skipped);
     void setFPPratio(int len);
@@ -288,6 +289,7 @@ class Regulator : public RingBuffer
     StdDev* pullStat          = nullptr;
     double mMsecTolerance     = 64;
     int mLastSeqNumOut        = -1;
+    int mStashedPacket        = -1;
     std::atomic<int> mLastSeqNumIn;
     QElapsedTimer mIncomingTimer;
     std::vector<double> mIncomingTiming;
index e42f6ad91af06b3d04cc1c4c15d9b17bdd366262..9006d764878de50dd2a090e88ca4813fa7dd92c9 100644 (file)
@@ -325,7 +325,8 @@ void UdpHubListener::receivedClientInfo(QSslSocket* clientConnection)
     // Assign server port and send it to Client
     if (id != -1) {
         cout << "JackTrip HUB SERVER: Sending Final UDP Port to Client: "
-             << mJTWorkers->at(id)->getServerPort() << endl;
+             << clientName.toStdString() << " = " << mJTWorkers->at(id)->getServerPort()
+             << endl;
     }
 
     if (id == -1
index d11dddcc04a1dbfeede882550dfdce8dfeae5de4..3a56e5d2d48d784f8816b11b7c537cb1f7ef89c1 100644 (file)
@@ -40,7 +40,7 @@
 
 #include "jacktrip_types.h"
 
-constexpr const char* const gVersion = "2.7.1";  ///< JackTrip version
+constexpr const char* const gVersion = "2.7.2";  ///< JackTrip version
 
 //*******************************************************************************
 /// \name Default Values
index 8ef59082931211ac55a05b7b027396b228e74d03..1310259ef1ecbdce1722d8bff08d34192b183500 100644 (file)
 
 QCoreApplication* createApplication(int& argc, char* argv[])
 {
+#ifdef __APPLE__
+    // Check for the DYLD_INSERT_LIBRARIES environment variable.
+    // Refuse to run if it is set, to avoid code injection attacks.
+    // Just an extra precaution since QtWebEngine requires the entitlement
+    // com.apple.security.cs.allow-dyld-environment-variable
+    // See https://doc.qt.io/qt-6/qtwebengine-deploying.html
+    if (getenv("DYLD_INSERT_LIBRARIES") != nullptr) {
+        std::cout << "Detected environment variable: DYLD_INSERT_LIBRARIES." << std::endl;
+        std::cout << "To run JackTrip, please omit the this environment variable."
+                  << std::endl;
+        std::exit(1);
+    }
+#endif
+
     // Check for some specific, GUI related command line options.
     bool forceGui = false;
     bool testGui  = false;
index d685645c92ee129b0423a3bfe8acc0ed017e92e7..3a8158562e7279f6aab5fd6242d1de7f998cd963 100644 (file)
@@ -1478,7 +1478,7 @@ void VirtualStudio::processError(const QString& errorMessage)
         msgBox.setWindowTitle(QStringLiteral("No JACK server"));
     } else if (errorMessage == QLatin1String("Peer Stopped")) {
         // Report the other end quitting as a regular occurance rather than an error.
-        msgBox.setText("The Studio has been stopped.");
+        msgBox.setText("Lost connection to the studio.");
         msgBox.setWindowTitle(QStringLiteral("Disconnected"));
     } else if (errorMessage.startsWith(RtAudioErrorMsg)) {
         if (errorMessage.length() > RtAudioErrorMsg.length() + 2) {